package de.metalcon.gwt.server.db;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.index.IndexManager;

import de.metalcon.gwt.server.exceptions.UncommitedNodeException;
import de.metalcon.gwt.shared.exceptions.RedundantCallException;

public class MCNode extends ANeoObject {
	private Node node;
	private static HashMap<String, MCNode> instances = new HashMap<String, MCNode>();
	private HashMap<String, String> properties = new HashMap<String, String>();

	public MCNode(GraphDatabaseService db, String key, String type) {
		super(db);

		if (instances.get(key) != null) {
			throw new RedundantCallException("Instance of Node with the key "
					+ key + " already Exists!");
		}

		setGlobalKey(key);
		setType(type);
		instances.put(key, this);

		setGlobalKey(key);
	}

	public MCNode(Node node) {
		super(node.getGraphDatabase());
		this.node = node;

		for (String key : this.node.getPropertyKeys()) {
			setProperty(key, (String) this.node.getProperty(key));
		}

		if (instances.get(getGlobalKey()) != null) {
			throw new RedundantCallException("Instance of Node with the key "
					+ getGlobalKey() + " already Exists!");
		}
		instances.put(getGlobalKey(), this);
	}

	public Node getNeoNode() {
		return node;
	}

	protected void setType(String type) {
		setProperty("type", type);
	}

	public String getType() {
		return getProperty("type");
	}

	HashMap<String, MCNode> getInstancelist() {
		return instances;
	}

	public String getGlobalKey() {
		return getProperty("key");
	}

	protected void setGlobalKey(String key) {
		setProperty("key", key);
	}

	public String getProperty(String key) {
		return properties.get(key);
	}

	public void setProperty(String key, String value) {
		this.properties.put(key, value);
	}

	public HashMap<String, String> getProperties() {
		return properties;
	}

	public void deleteProperty(String key) {
		properties.remove(key);
	}

	public void addRelationshipTo(MCNode relatedNode, RelationshipType type) {
		if(relatedNode.getNeoNode() == null){
			throw new UncommitedNodeException("You need to commit a node before you cann add relations to it!");
		}
		node.createRelationshipTo(relatedNode.getNeoNode(), type);
	}

	/*
	 * Write content to DB
	 */
	@Override
	public void commit() {
		Transaction tx = db.beginTx();
		IndexManager index = db.index();
		Index<Node> nodes = index.forNodes("anyNode");
		try {
			if(node == null){
				node = db.createNode();
			}
			
			for (Map.Entry<String, String> entry : getProperties().entrySet()) {
				node.setProperty(entry.getKey(), entry.getValue());
			}

			nodes.add(node, "key", getGlobalKey());

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public List<MCNode> getRelatedNodes(RelationshipType t, Direction dir) {
		LinkedList<MCNode> nodes = new LinkedList<MCNode>();

		for (Relationship rel : node.getRelationships(t, dir)) {
			if (dir == Direction.INCOMING || dir == Direction.BOTH) {
				nodes.add(MCNode.Get(rel.getStartNode()));
			}

			if (dir == Direction.OUTGOING || dir == Direction.BOTH) {
				nodes.add(MCNode.Get(rel.getEndNode()));
			}
		}

		return nodes;
	}

	public static MCNode Get(GraphDatabaseService db, String key) {
		MCNode mcNode = (MCNode) instances.get(key);
		if (mcNode == null) {

			IndexManager index = db.index();
			Index<Node> nodes = index.forNodes("anyNode");

			IndexHits<Node> hits = nodes.get("Key", key);
			Node node = hits.getSingle();
			if (node != null) {
				mcNode = new MCNode(node);
			}
		}
		return mcNode;
	}

	public static MCNode Get(Node node) {
		MCNode mcNode = (MCNode) instances.get(node.getProperty("key"));
		if (mcNode == null) {
			mcNode = new MCNode(node);
		}
		return mcNode;
	}

	public static MCNode Create(GraphDatabaseService db, String key, String type) {
		MCNode mcNode = new MCNode(db, key, type);
		return mcNode;
	}
}